前面 binder 原理和通信模型中在接口实现部分(Bp 和 Bn)中应该看到很多地方都有使用 parcel。这个 android 专门设计用来跨进程传递数据的,实现在 native,java 层有接口(基本上是 jni 马甲)。照例先说下源代码位置(4.4 的):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| frameworks/base/core/java/os/Parcel.java frameworks/base/core/java/os/Parcelable.java frameworks/base/core/java/os/ParcelFileDescriptor.java frameworks/base/core/java/os/MemoryFile.java frameworks/base/core/jni/android_os_Parcel.h frameworks/base/core/jni/android_os_MemoryFile.h frameworks/base/core/jni/android_os_Parcel.cpp frameworks/native/include/binder/Parcel.h frameworks/native/include/binder/IMemory.h frameworks/native/include/binder/MemoryHeapBase.h frameworks/native/include/binder/MemoryBase.h frameworks/native/libs/binder/Parcel.cpp frameworks/native/libs/binder/Memory.cpp frameworks/native/libs/binder/MemoryHeapBase.cpp frameworks/native/libs/binder/MemoryBase.cpp kernel/drivers/staging/android/binder.h kernel/drivers/staging/android/binder.c kernel/include/linux/ashmem.h kernel/mm/ashmem.c
|
原理
在 java 层 parcel 有个接口叫 Parcelable,和 java 的 Serializable 很像,刚开始我还没搞明白这2个有什么区别(以前对 java 也不太熟)。这里简单说一下, Serializable 是 java 的接口,翻译过来是序列化的意思,就是通过实现这个接口能够让 java 的对象序列化能够永久保存在的存储介质上,然后反序列化就能从存储介质上实列化出 java 对象(通俗点,就是一个 save/load 的功能)。因为保存到了存储介质上,所以是可以跨进程的(一个进程把数据写入文件,另外一个去读)。但是为什么 android 还要搞一个 parcel 出来,是因为 java 的 Serializable 是通过存储介质的,所以速度慢。parcel 是基于内存传递的,比磁盘I/O要块,而且更加轻量级(这个我是从网上看到的,我没研究过 java 的 Serializable 代码)。
parcel 在内存中的结构是一块连续的内存,会动根据需要自动扩展大小(这个设计比较赞,一些对性能要求不是太高、或是小数据的地方,可以不用废脑想分配多大空间)。parcel 传递数据,可以分为3种,传递方式也不一样:
- 小型数据: 从用户空间(源进程)copy 到 kernel 空间(binder 驱动中)再写回用户空间(目标进程,binder 驱动负责寻找目标进程)。
- 大型数据: 使用 android 的匿名共享内存(Ashmem)传递
- binder 对象: kernel binder 驱动专门处理
下面逐一分析。这里我打算从 natvie 到 kernel 再到 java 的顺序进行,因为接着前面通信原型那里,所以从 natvie 开始会比较好,而且实现的地方也在 native。
小型数据
先来看看 Parcel.h 中几个比较关键的几个变量:
1 2 3 4 5 6 7 8 9
| uint8_t* mData; size_t mDataSize; size_t mDataCapacity; mutable size_t mDataPos; size_t* mObjects; size_t mObjectsSize; size_t mObjectsCapacity; mutable size_t mNextObjectHint;
|
- mData: 数据指针,也是数据在本进程空间内的内存地址
- mDataSize: 存储的数据大小(使用的空间大小)
- mDataCapacity: 数据空间大小,如果不够的话,可以动态增长
- mDataPos: 数据游标,当前数据的位置,和读文件的游标类似,可以手动设置。声明了 mutbale 属性,可以学习下这个属性应该用在声明地方 ^_^。
- mObjects:
flat_binder_object
对象的位置数据,注意这个是个指针(其实就是个数组),里面保存的不是数据,而且地址的偏移(后面再具体说)。
- mObjectsSize: 这个简单来说其实就是上面那个 objects 数组的大小。
- mObjectsCapacity: objects 偏移地址(再次强调一次是地址)的空间大小,同样可以动态增长
- mNextObjectHint: 可以理解为 objects 的 dataPos 。
还记得通信模型 IPCThreadState 中有2个 Parcel 变量: mIn、mOut,前面分析这2个东西是 binder 通信的时候打包数据用的。我们通过结合前面的例子来分析。
首先是初始化,IPCThreadState 是直接使用变量的(栈内存),使用默认构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| Parcel::Parcel() { initState(); } void Parcel::initState() { mError = NO_ERROR; mData = 0; mDataSize = 0; mDataCapacity = 0; mDataPos = 0; ALOGV("initState Setting data size of %p to %d\n", this, mDataSize); ALOGV("initState Setting data pos of %p to %d\n", this, mDataPos); mObjects = NULL; mObjectsSize = 0; mObjectsCapacity = 0; mNextObjectHint = 0; mHasFds = false; mFdsKnown = true; mAllowFds = true; mOwner = NULL; }
|
初始话很简单,几乎都是初始化为 0(NULL) 的。然后看看 IPCThreadState 使用的初始化:
1 2 3 4 5 6 7 8 9 10 11 12
| IPCThreadState::IPCThreadState() : mProcess(ProcessState::self()), mMyThreadId(androidGetTid()), mStrictModePolicy(0), mLastTransactionBinderFlags(0) { pthread_setspecific(gTLS, this); clearCaller(); mIn.setDataCapacity(256); mOut.setDataCapacity(256); }
|
首先调用 setDataCapacity 来初始化 parcel 的数据空间大小:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| status_t Parcel::setDataCapacity(size_t size) { if (size > mDataCapacity) return continueWrite(size); return NO_ERROR; } status_t Parcel::continueWrite(size_t desired) { ... ... if (mOwner) { ... ... } else if (mData) { ... ... } else { uint8_t* data = (uint8_t*)malloc(desired); if (!data) { mError = NO_MEMORY; return NO_MEMORY; } if(!(mDataCapacity == 0 && mObjects == NULL && mObjectsCapacity == 0)) { ALOGE("continueWrite: %d/%p/%d/%d", mDataCapacity, mObjects, mObjectsCapacity, desired); } mData = data; mDataSize = mDataPos = 0; ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); mDataCapacity = desired; } return NO_ERROR; }
|
设置空间大小的,其实主要是调用到了 contiueWrite 函数。前面那个 size > mDataCapacity 判断意思是如果设置的大小比原来的要大,则需要调整申请的内存的大小,如果小的话,就直接使用原来的大小。
接下来看 contiueWrite,前面有个 object 的判断先不管。然后下面主要是分开3个分支,分别是:
- 分支一: 如果设置了 release 函数指针(mOwner是个函数指针),调用 release 函数进行处理。
- 分支二: 没有设置 release 函数指针,但是 mData 中存在数据,需要在原来的数据的基础上扩展存储空间。
- 分支三: 没有设置 release 函数指针,并且 mData 中不存在数据(就是注释中说的第一次使用, Easy -_-||),调用 malloc 申请内存块,保存在 mData。设置相应的设置 capacity、size、pos、object 的值。
这里先贴出分支三的代码,第一次使用,是走分支三的,其它2个后面再说。这里注意一点,这里只 malloc 了一个块内存,就是 mData 的,前面说 parcel 存储结构是一块连续的内存,mObjects 只是保存的只是地址的偏移,这里可以看到一些端倪(后面就能清楚)。
初始化了之后,我们看看怎么使用的,在通信模型中我们说道 Bp 端发起 IPC 调用,通过 IPCThreadState 对 binder 驱动写入请求数据发送到 Bn 端,我们回想下 Bp 端写数据的地方:
1 2 3 4 5 6 7 8 9 10 11 12 13
| status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer) { binder_transaction_data tr; ... ... mOut.writeInt32(cmd); mOut.write(&tr, sizeof(tr)); return NO_ERROR; }
|
先看前面 writeInt32 这个,对 parcel 写入一个 32bit 的 int 型数据。parcel 接口中有一类是专门针对基本类型(int、float、double、int数组),writeInt32、writeInt64、writeFloat、writeDouble、writeIntArray 这些(对应有 read 接口)。然后他们都是另一个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| template<class T> status_t Parcel::writeAligned(T val) { COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T)); if ((mDataPos+sizeof(val)) <= mDataCapacity) { restart_write: *reinterpret_cast<T*>(mData+mDataPos) = val; return finishWrite(sizeof(val)); } status_t err = growData(sizeof(val)); if (err == NO_ERROR) goto restart_write; return err; } status_t Parcel::finishWrite(size_t len) { mDataPos += len; ALOGV("finishWrite Setting data pos of %p to %d\n", this, mDataPos); if (mDataPos > mDataSize) { mDataSize = mDataPos; ALOGV("finishWrite Setting data size of %p to %d\n", this, mDataSize); } return NO_ERROR; }
|
writeAligned 看名字就知道要内存对齐,第一句好像就是验证下是否内存对齐的,好像能够根据编译选项判断,应该是如果打开某个编译选项,如果传过来的 size 没内存对齐直接报错吧,内存对齐的算法都是搞一些位运算(这里好像是4字节对齐吧):
#define PAD_SIZE(s) (((s)+3)&~3)
int32、int64、float、double 都是4字节对齐的。接着往下看,有个判断当前 pos + 要写入的数据的所占用的空间是否比 capacity 大,就是看空间是不是够大。前面所了 parcel 能够根据需求自动增长空间,这里我们先看空间够的情况,就是走 if 里面:
*reinterpret_cast(mData+mDataPos) = val;
直接取当前地址强制转化指针类型,然后赋值(c/c++语言就是舒服)。然后调用 finishWrite 完成写入。finishWrite 就是把 mDataPos 和 mDataSize 值改了一下(加上刚刚写入数据的大小),从这里可以看得出,对于 write 来说,mDataPos = mDataSize。
然后我们看看当空间不够的情况,就是走 if 后面,有一个 growData 的函数,这个是用来调整内存空间的,然后一个 goto 跳转回 if 里面重写写入(parcel 的实现很多地方有 goto,其实 goto 在本函数里面用还好)。我们来看看 growData:
1 2 3 4 5 6 7 8
| status_t Parcel::growData(size_t len) { size_t newSize = ((mDataSize+len)*3)/2; return (newSize <= mDataSize) ? (status_t) NO_MEMORY : continueWrite(newSize); }
|
这里 parcel 的增长算法: ((mDataSize+len)*3)/2, 带一定预测性的增长,避免频繁的空间调整(每次调整需要重新 malloc 内存的,频繁的话会影响效率的)。然后这里有个判断 newSize < mDataSize 就认为 NO_MEMORY。这是所如果如果溢出了(是负数),就认为申请不到内存了。然后调用的函数是 continueWrite ,和前面 setCapacity 调用的是同一个。前面说这个函数有3个分支,这里我们就可以来看第2个分支了(mData 有数据的情况):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| status_t Parcel::continueWrite(size_t desired) { size_t objectsSize = mObjectsSize; if (desired < mDataSize) { if (desired == 0) { objectsSize = 0; } else { while (objectsSize > 0) { if (mObjects[objectsSize-1] < desired) break; objectsSize--; } } } if (mOwner) { ... ... } else if (mData) { if (objectsSize < mObjectsSize) { const sp<ProcessState> proc(ProcessState::self()); for (size_t i=objectsSize; i<mObjectsSize; i++) { const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(mData+mObjects[i]); if (flat->type == BINDER_TYPE_FD) { mFdsKnown = false; } release_object(proc, *flat, this); } size_t* objects = (size_t*)realloc(mObjects, objectsSize*sizeof(size_t)); if (objects) { mObjects = objects; } mObjectsSize = objectsSize; mNextObjectHint = 0; } if (desired > mDataCapacity) { uint8_t* data = (uint8_t*)realloc(mData, desired); if (data) { mData = data; mDataCapacity = desired; } else if (desired > mDataCapacity) { mError = NO_MEMORY; return NO_MEMORY; } } else { if (mDataSize > desired) { mDataSize = desired; ALOGV("continueWrite Setting data size of %p to %d\n", this, mDataSize); } if (mDataPos > desired) { mDataPos = desired; ALOGV("continueWrite Setting data pos of %p to %d\n", this, mDataPos); } } } else { ... ... } return NO_ERROR; }
|
这里有关 object 的处理也是先放放,后面再一起说。看后面的,如果需要的空间比原来的大,那么调用 realloc 把空间调整一下。realloc 可以看 man,是说保留原来的内存空间,然后尝试在原来的空间后面扩展需要的内存空间。然后就是把 mDataPos 和 mDataSize 设置一下。如果是要的空间比原来的小,那就什么都不干,就是说用就当成小的用,内存还是以前那么大。
然后回到 IPCThreadState::writeTransactionData 我们看看后面那个 mOut.write:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| status_t Parcel::write(const void* data, size_t len) { void* const d = writeInplace(len); if (d) { memcpy(d, data, len); return NO_ERROR; } return mError; } void* Parcel::writeInplace(size_t len) { const size_t padded = PAD_SIZE(len); if (mDataPos+padded < mDataPos) { return NULL; } if ((mDataPos+padded) <= mDataCapacity) { restart_write: uint8_t* const data = mData+mDataPos; if (padded != len) { #if BYTE_ORDER == BIG_ENDIAN static const uint32_t mask[4] = { 0x00000000, 0xffffff00, 0xffff0000, 0xff000000 }; #endif #if BYTE_ORDER == LITTLE_ENDIAN static const uint32_t mask[4] = { 0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff }; #endif *reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len]; } finishWrite(padded); return data; } status_t err = growData(padded); if (err == NO_ERROR) goto restart_write; return NULL; }
|
这个函数首先调用 writeInplace。来看下 writeInplace,这个函数参数是一个大小,返回是一个地址。进去里面看下,除去字节对齐的部分,就是把 mDataPos 和 mDataSize 的值加上了传过去的 len 大小,然后返回 mData + len 的地址。注意这里也有空间不够的情况,和前面的处理一样,调用 growData 去调整空间(parcel 写的接口基本上都有这个 growData 的处理)。这个函数相当于是帮你把内存分配好,然后返回计算好的起始地址给你。然后回到 write 下面直接 memcpy,把传过来的地址中的数据复制过来。
这2个接口的示例很经典,一个是写基本类型,一个是写对象类型的。基本类型可以说是值类型,直接把值写入内存中;对象类型,是把对象的内存数据写进来,这个相当于 c++ 里面的深拷贝,复制数据。上面 IPCThreadState 写入的是 binder_transaction_data
这个结构体,后面具体说说 binder 通信之间的数据格式。现在再来看看 writeString8 这个接口,加深下理解:
1 2 3 4 5 6 7 8 9 10 11 12
| status_t Parcel::writeString8(const String8& str) { status_t err = writeInt32(str.bytes()); if (str.bytes() > 0 && err == NO_ERROR) { err = write(str.string(), str.bytes()+1); } return err; }
|
String8 是 android 在 native 对 char*
封装了一下,有点像 java 的 String方便字符串操作的。这个也是对象类型,看到 parcel 先是把 String8 的 size 写进去,然后 write 把 char*
数据写在 size 后面的内存中。我们再来看看对应的 readString8:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| String8 Parcel::readString8() const { int32_t size = readInt32(); if (size > 0 && size < INT32_MAX) { const char* str = (const char*)readInplace(size+1); if (str) return String8(str, size); } return String8(); } status_t Parcel::readInt32(int32_t *pArg) const { return readAligned(pArg); } template<class T> status_t Parcel::readAligned(T *pArg) const { COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T)); if ((mDataPos+sizeof(T)) <= mDataSize) { const void* data = mData+mDataPos; mDataPos += sizeof(T); *pArg = *reinterpret_cast<const T*>(data); return NO_ERROR; } else { return NOT_ENOUGH_DATA; } } const void* Parcel::readInplace(size_t len) const { if ((mDataPos+PAD_SIZE(len)) >= mDataPos && (mDataPos+PAD_SIZE(len)) <= mDataSize) { const void* data = mData+mDataPos; mDataPos += PAD_SIZE(len); ALOGV("readInplace Setting data pos of %p to %d\n", this, mDataPos); return data; } return NULL; }
|
读是先 readInt32 把 write 写入的 size 取出来,readInt32 是调用 readAligned 的。和前面 writeAligned 对应,先是判断下字节对齐,然后直接取 mData + mDataPos 地址的数据,转化成模版类型(再次感叹一次 c、c++ 爽)。注意一下这个函数最后移动了 mDataPos 的位置(对应 read 的数据的大小)。
然后是调用 readInplace ,有了前面的说明,你也应该知道这个是去返回对应的 writeInplace 的地址。里面果然是,同样注意最后移动了 mDataPos 的位置。然后去返回的地址取之前写入的 char*
数据,基于上面的数据重新构造出新的 String8 对象。这里你看出 pacrel 和 Serializable 很像,只不过 parcel 是在内存中捣腾,还有后面你会发现 parcel 还为 binder 做了一些别的事情。
还有前面说的 read 的接口回自动移动 mDataPos 的位置(parcel 所有 read 的接口都会自动移动 mDataPos),然后看前面的代码,你会发现,write 之后,到 read 的时候,能否取得到正确的数据,依赖于 mDataPos 的位置。这里就要求 binder 通信的时候,双方在用 parcel 读写数据的时候顺序一定要一致。例如说一个 IPC 调用,传递一个函数的参数: int、float、object,用 parcel 写顺序是: writeInt32、writeFloat、write,那么对方接到传过来的 parcel read 的顺序也必须为: readInt32、readFloat、read。就算其中某些参数你不用,你也要 read 一下,主要是要把 mDataPos 的位置移动对。
这里可以看得出 parcel 只是提供的是一块连续的内存块,至于往里面写什么东西,格式是怎么样的,取决于使用的人,所以使用人要要保证自己读得正确(要和写对应),例如前面说的 String8,前面一个 int32 是大小,后面这个大小的是 char*
数据,这个读的人必须按这个格式才重新创建出 String8。这个我们后面看 binder 中的使用能够看得出来。
接下来我们看看 binder 怎么把 parcel 打包的数据传递给另外一个进程的。这里我们结合下通信模型那分析的东西。首先 Bp 端调用 writeTransationData 把 IPC 请求打包发送到 binder 驱动。前面看到打包的是一个 cmd 和一个 binder_transaction_data
结构。先来看看这个结构(在 kernel binder.h 中):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| struct binder_transaction_data { union { size_t handle; void *ptr; } target; void *cookie; unsigned int code; unsigned int flags; pid_t sender_pid; uid_t sender_euid; size_t data_size; size_t offsets_size; union { struct { const void *buffer; const void *offsets; } ptr; uint8_t buf[8]; } data; };
|
然后我们看看 Bp 端对这个结构体填充了什么:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags, int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer) { binder_transaction_data tr; tr.target.handle = handle; tr.code = code; tr.flags = binderFlags; tr.cookie = 0; tr.sender_pid = 0; tr.sender_euid = 0; const status_t err = data.errorCheck(); if (err == NO_ERROR) { tr.data_size = data.ipcDataSize(); tr.data.ptr.buffer = data.ipcData(); tr.offsets_size = data.ipcObjectsCount()*sizeof(size_t); tr.data.ptr.offsets = data.ipcObjects(); } else if (statusBuffer) { tr.flags |= TF_STATUS_CODE; *statusBuffer = err; tr.data_size = sizeof(status_t); tr.data.ptr.buffer = statusBuffer; tr.offsets_size = 0; tr.data.ptr.offsets = NULL; } else { return (mLastError = err); } mOut.writeInt32(cmd); mOut.write(&tr, sizeof(tr)); return NO_ERROR; }
|
binder_transatcion_data
target 这个 union 先看注释的说明, target.handle 是说是 IPC 目标的标示,这个 handle 这个东西后面再细说。code 是 IPC 接口定义的接口的标示(例如 START_ACTIVITY
, GET_TASK
之类的玩意)。然后是检查下 parcel 的错误状态,一般是没啥错误的。然后后面几个赋值,来看看从 parcel 取出的是什么东西:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const uint8_t* Parcel::ipcData() const { return mData; } size_t Parcel::ipcDataSize() const { return (mDataSize > mDataPos ? mDataSize : mDataPos); } const size_t* Parcel::ipcObjects() const { return mObjects; } size_t Parcel::ipcObjectsCount() const { return mObjectsSize; }
|
看注释 binder_transatcion_data
的 data 这个变量是个 union,远程传输的时候用的是 ptr 这个结构,里面保存的是数据的地址。ptr.buffer 是 parcel 的 ipcData() ,这个函数返回的是 mData 就是数据地址。注意一下这里 data.ptr 保存的是 IPC Bp 传入的那个 parcel,不是 IPCThreadState mOut 这个用来打包 binder 数据的 parcel(都是用 parcel 容易搞混)。这里 data 这个 parcel 是将 IPC 的接口函数的参数数据打包起来的,例如 int、string 之类的参数。Bn 端返回的数据也是通过 parcel 打包的。而 IPCThreadState 的 mOut 只是写入了 cmd 和 binder_transatcion_data
而已,而 binder_transation_data
保存了 IPC 中传递的真正数据的地址(从参数 parcel 或取的),仅仅是地址而已。所以开头为什么 mOut 和 mIn 只把空间大小设置为 256,刚开始以为是因为 parcel 可以动态增长空间,先在看来,其实根本用不了到 256,因为数据大小只有一个 int32 的 cmd 和 binder_transation_data
这个结构而已。算一下 int32 4字节,binder_transation_data
第一个 target union 2个都是4字节的地址,所以就是4字节,除去后面 data 的 union 其余的7个都是4字节的地址,后面那个 data union 算最大的数据,是 4字节x2,所以 binder_transation_data
结构占 40字节,加上 cmd 就是 44字节。回去通信模型那看看图,是不是 Bp 端发 BC_TRANSACTION
write_size 是不是 44。mIn 从 Bn 那读回来的数据也是差不多的,所以 256 足够了,基本上不需要动态调整空间的。
好,回到赋值那,看看后面几个,data_size
是取 mDataSize, mDataPos 比较大的那个(估计是为了保险吧,对于写 mDataSize 应该等于 mDataPos),然后看看后面的把 parcel 的 mObjectsSize 和 mObjects 分别给了 offset_size
和 ptr.offsets,offset_size
还乘了个地址的大小。前面说过了 parcel 的 mObjects 保存的是偏移地址,parcel 的名字很奇怪,kernel 里面的数据结构用名字再次告诉了我们这个是偏移地址。这个到后面就清楚了。
好 mOut 把数据打包好了,到了 waitForResponse 循环调用 talkWithDriver 向 binder 驱动写数据,以及等待 Bn 端返回数据(忘记了的回通信模型看看流程图)。我们先来看第一次写通信命令写了什么东西进去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| status_t IPCThreadState::talkWithDriver(bool doReceive) { if (mProcess->mDriverFD <= 0) { return -EBADF; } binder_write_read bwr; const bool needRead = mIn.dataPosition() >= mIn.dataSize(); const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0; bwr.write_size = outAvail; bwr.write_buffer = (long unsigned int)mOut.data(); if (doReceive && needRead) { bwr.read_size = mIn.dataCapacity(); bwr.read_buffer = (long unsigned int)mIn.data(); } else { bwr.read_size = 0; bwr.read_buffer = 0; } ... ... if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR; bwr.write_consumed = 0; bwr.read_consumed = 0; status_t err; do { ... ... if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0) err = NO_ERROR; else err = -errno; ... ... } while (err == -EINTR); ... ... return err; }
|
我们在 kernel 的 binder.h 看到 BINDER_WRITE_READ
的参数是 binder_write_read
这个结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read) struct binder_write_read { signed long write_size; signed long write_consumed; unsigned long write_buffer; signed long read_size; signed long read_consumed; unsigned long read_buffer; };
|
binder_write_read
的结构并不复杂,就是一个数据地址,一个数据大小,一个数据确认处理的大小,分为2部分,write 和 read(看注释后面要支持 64bit binder 数据传输这里要改不少东西吧)。回来看下赋值。前面那个那个判断 mIn 中的是否有读的数据,是通过 mDataPos 的位置来判断的,就是说如果 mDataPos 的位置比 mDataSize 小,说明还有数据还没读完,前面说了 parcel 每调用一次 read 接口就会自动移动 mDataPos,如果正好把 read 次数(Bp 端读)对应上 write 次数(Bn 端写),那么 mDataPos 是正好等于 mDataSize 的。后面根据 (!doReceive || needRead) 决定 write_size
的大小,这个后面到 kernel 里可以知道 size 的大小是否为 0 决定了是否调用 binder 驱动的读写处理函数。如果 mIn 中还有数据还没读取完,needRead 为 true, doReceive 默认是 true(默认要接收 Bn 端返回的数据),所以如果还有 Bn 端发过来的数据还没读完,本次循环在 binder 驱动中是发不出数据的。这里开始是能没有读数据的,所以能发得出来,write_size
大小是 mOut parcel 的 mDataSize,write_buffer
是 mOut 的 mData 地址。读的部分相应的取 mIn 的,这里给接收的大小也是 256,后面可以看到 Bn 端发过来也是也是 binder_transatcion_data
结构,所以 256 也够了。然后在 ioctl 前把 consumed 都设置成0。
然后就 ioctl 到 kernel 的 binder 驱动里面去了,我在 binder 驱动中看看,parcel 是怎么从 Bp 端传递到 Bn 端(或者从 Bn 返回到 Bp)的。首先是上面的 Bp 向 binder 发送 BC_TRANSACTION
把 binder_transtion_data
的地址保存到 ioctl 的参数 binder_write_read
中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int ret; struct binder_proc *proc = filp->private_data; struct binder_thread *thread; unsigned int size = _IOC_SIZE(cmd); void __user *ubuf = (void __user *)arg; ... ... switch (cmd) { case BINDER_WRITE_READ: { struct binder_write_read bwr; if (size != sizeof(struct binder_write_read)) { ret = -EINVAL; goto err; } if (copy_from_user(&bwr, ubuf, sizeof(bwr))) { ret = -EFAULT; goto err; } ... ... if (bwr.write_size > 0) { ret = binder_thread_write(proc, thread, (void __user *)bwr.write_buffer, bwr.write_size, &bwr.write_consumed); if (ret < 0) { bwr.read_consumed = 0; if (copy_to_user(ubuf, &bwr, sizeof(bwr))) ret = -EFAULT; goto err; } } if (bwr.read_size > 0) { ret = binder_thread_read(proc, thread, (void __user *)bwr.read_buffer, bwr.read_size, &bwr.read_consumed, filp->f_flags & O_NONBLOCK); if (!list_empty(&proc->todo)) wake_up_interruptible(&proc->wait); if (ret < 0) { if (copy_to_user(ubuf, &bwr, sizeof(bwr))) ret = -EFAULT; goto err; } } ... ... if (copy_to_user(ubuf, &bwr, sizeof(bwr))) { ret = -EFAULT; goto err; } break; } ... ... default: ret = -EINVAL; goto err; } ... ... }
|
这里有个 kernel 函数调用: copy_from_user
,是从用户空间 copy 指定的一段内存数据到 kernel 空间(用户态(空间),kernel态(空间)有啥区别网上查吧,我也不是很清除),这样 IPCThreadState talkWithDriver 那些填写的那个 binder_write_read
就传递到 kernel binder 驱动中了。这里可以看得到,如果 IPCThreadState 把 wirte 或是 read 的 size 设置为 0 的话就不会处理(前面也说过)。我们先看 write, write_buffer
里面的数据是 IPCThreadState 用 mOut 打包的内存数据块。根据前面的分析,应该是这样的格式:
然后去 binder_thread_write
里面去看看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| int binder_thread_write(struct binder_proc *proc, struct binder_thread *thread, void __user *buffer, int size, signed long *consumed) { uint32_t cmd; void __user *ptr = buffer + *consumed; void __user *end = buffer + size; while (ptr < end && thread->return_error == BR_OK) { if (get_user(cmd, (uint32_t __user *)ptr)) return -EFAULT; ptr += sizeof(uint32_t); switch (cmd) { ... ... case BC_TRANSACTION: case BC_REPLY: { struct binder_transaction_data tr; if (copy_from_user(&tr, ptr, sizeof(tr))) return -EFAULT; ptr += sizeof(tr); binder_transaction(proc, thread, &tr, cmd == BC_REPLY); break; } ... ... default: printk(KERN_ERR "binder: %d:%d unknown command %d\n", proc->pid, thread->pid, cmd); return -EINVAL; } *consumed = ptr - buffer; } return 0; }
|
这里有个 while 循环,结束条件是 ptr 指针移动 end 处,就是处理完 IPCThreadState write 进来的数据为止。循环一开始就用 get_user
从 ptr 指向的用户空间出一个 int32 的数据到 kernel 空间(get_user
和 copy_from_user
的区别是,一个是 copy 一个简单的变量,一个是 copy 一块内存块)。然后接着把 ptr 指针移动一个 int32 大小。这里注意下,前面 ioctl 那 copy_from_user
是从用户空间得到 binder_write_read
结构(地址在 ioctl 的参数里面),而这里从用户空间 copy 的是保存在 binder_write_read
write_buffer
中的地址,也就是前面 mOut 的 mData 的地址。所以要根据前面打包的格式来读(看上面的图)。前面说了 parcel 的读和写对应。所以这里先取 cmd(是 BC_TRANSACTION
),然后 parcel 调用 read 接口会自动移动 mDataPos ,binder 驱动里面要自己手动移动指针位置(这里再次看出,parcel 提供简单的内存读写,很灵活,也比较简单,但是同时也比较容易出错)。然后后面继续 copy_from_user
从用户态的 mOut 地址把 binder_transation_data
copy 过来(顺带移动指针),然后交由 binder_transation
函数处理(这篇的流程其实和前面通信模型是一样的,但是本篇主要讲数据的流动)。这里先看到后面,binder_transation
处理完后, consumed 就被设置为相应读取的数据大小(这个 consumed 是个指针,其实就设置 binder_write_read
这个结构 write_consumed
这个变量的, binder_write_read
这个结构最后又会被传回用户空间去的,后面能看到)。
至于 binder_transation
中怎么传递到另一个进程中的去,去看我下一篇 binder 的内存管理篇吧,那里有详细的说明,这里不多说这些。反正最后通过 binder_thread_read
传递用 Bn 端的用户空间,然后借着上一篇 Bn 端的 getAndExecuteCommand 从 talkWithDriver 那的 ioctl 返回得到 Bp 端通过 kernel 发送的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| status_t IPCThreadState::getAndExecuteCommand() { status_t result; int32_t cmd; result = talkWithDriver(); if (result >= NO_ERROR) { size_t IN = mIn.dataAvail(); if (IN < sizeof(int32_t)) return result; cmd = mIn.readInt32(); IF_LOG_COMMANDS() { alog << "Processing top-level Command: " << getReturnString(cmd) << endl; } result = executeCommand(cmd); ... ... return result; }
|
Bn 端等待 Bp 端的时候,把自己 mIn 的 parcel 的 buffer 传递到 kernel 里面去了,所以 Bp 端发送过来的 parcel 通过 kernel 传递到 Bn 端的 mIn 中去了。
内存管理篇那里 binder_thread_read
会把 cmd 写入 mIn buffer 的第一个 int32 的地址,所以这里先读 int32 的 cmd,然后送给 executeCommand 处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| status_t IPCThreadState::executeCommand(int32_t cmd) { BBinder* obj; RefBase::weakref_type* refs; status_t result = NO_ERROR; switch (cmd) { ... ... case BR_TRANSACTION: { binder_transaction_data tr; result = mIn.read(&tr, sizeof(tr)); ALOG_ASSERT(result == NO_ERROR, "Not enough command data for brTRANSACTION"); if (result != NO_ERROR) break; Parcel buffer; buffer.ipcSetDataReference( reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer), tr.data_size, reinterpret_cast<const size_t*>(tr.data.ptr.offsets), tr.offsets_size/sizeof(size_t), freeBuffer, this); ... ... Parcel reply; if (tr.target.ptr) { sp<BBinder> b((BBinder*)tr.cookie); const status_t error = b->transact(tr.code, buffer, &reply, tr.flags); if (error < NO_ERROR) reply.setError(error); } else { const status_t error = the_context_object->transact(tr.code, buffer, &reply, tr.flags); if (error < NO_ERROR) reply.setError(error); } if ((tr.flags & TF_ONE_WAY) == 0) { LOG_ONEWAY("Sending reply to %d!", mCallingPid); sendReply(reply, 0); } else { LOG_ONEWAY("NOT sending reply to %d!", mCallingPid); } mCallingPid = origPid; mCallingUid = origUid; } break; default: printf("*** BAD COMMAND %d received from Binder driver\n", cmd); result = UNKNOWN_ERROR; break; } if (result != NO_ERROR) { mLastError = result; } return result; } status_t Parcel::read(void* outData, size_t len) const { if ((mDataPos+PAD_SIZE(len)) >= mDataPos && (mDataPos+PAD_SIZE(len)) <= mDataSize) { memcpy(outData, mData+mDataPos, len); mDataPos += PAD_SIZE(len); ALOGV("read Setting data pos of %p to %d\n", this, mDataPos); return NO_ERROR; } return NOT_ENOUGH_DATA; }
|
继续看前一篇的那张通信模型的图, Bn 这里是接到的 cmd 是 kernel 发过来的 BR_TRANSACTION
, 然后前面 Bp 把 binder_transaction_data
通过 Parcel 写入,这里就要通过 read 来读出来了。内存流的,直接一个强制转化就行了,read 也很简单,就是 memcpy (可以好好看看内存管理篇,kernel 里面传递 parcel data 的 buffer 的技巧很牛x)。然后这里的 Parcel buffer 是临时变量, ipcSetDataReference 设置 freeBuffer 函数怎么回事,内存管理篇都有讲,这里就不多说了。然后最后
b->transact(tr.code, buffer, &reply, tr.flags)
就把 Bp 传递过来的 parcel 传递子类实现 binder 业务的 transact 函数去处理的,顺带,把放返回值的 reply 给传过去了。
我们拿 AM 中的一个简单的接口来看一下(frameworks/base/core/java/android/app/ActivityManagerNative.java):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| public boolean finishActivity(IBinder token, int resultCode, Intent resultData) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); data.writeInt(resultCode); if (resultData != null) { data.writeInt(1); resultData.writeToParcel(data, 0); } else { data.writeInt(0); } mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0); reply.readException(); boolean res = reply.readInt() != 0; data.recycle(); reply.recycle(); return res; } @Override public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { switch (code) { ... ... case FINISH_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); Intent resultData = null; int resultCode = data.readInt(); if (data.readInt() != 0) { resultData = Intent.CREATOR.createFromParcel(data); } boolean res = finishActivity(token, resultCode, resultData); reply.writeNoException(); reply.writeInt(res ? 1 : 0); return true; } ... ... } return super.onTransact(code, data, reply, flags); }
|
上面看代码中的注释就差不多了,顺序都是一一对应的。然后说说那个 Parcelable 这个接口。这个接口最主要就是2个函数:writeToParcel、createFromParcel 这2个,一个相当于是序列化,一个是反序列化。就是自己类自己实现了的,再复杂的对象都可以通过 Parcel 前面的那些基本类型来存储。
大型数据
大型数据主要是通过 Parcel 的匿名共享内存(Ashmem)接口来使用的(writeBlob、readBlob),当然你也可以使用 writeInPlace 使用普通内存来传递,效率么,呵呵(还有不能超过 binder 的 1MB 大小的限制哦)。 这个话去看我的那篇专门说 ashmem 的吧。
binder 对象
Parcel 有一个特殊的结构叫 flat_binder_object
。这个是专门用来传递 binder 对象的(其实这个在 ashmem 篇里发现这个还可以传递文件描述 fd,咋这只说 binder 句柄)。
这里我略过 Parcel 的 java 接口 和 jni 的马甲了,直接拿 native 的代码说,稍微简洁一些。
我还是以上面 AM 里面那个例子说。里面在 IPC 有个要传递的对象是 IBinder 类型的,通过原型篇的分析,这个就是 binder 对象了,这里说的 binder 句柄传递,就是针对这一类型的数据。这里就是调用了 Parcel 的2个接口,我们先来看看写的那个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| status_t Parcel::writeStrongBinder(const sp<IBinder>& val) { return flatten_binder(ProcessState::self(), val, this); } status_t flatten_binder(const sp<ProcessState>& proc, const sp<IBinder>& binder, Parcel* out) { flat_binder_object obj; obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; if (binder != NULL) { IBinder *local = binder->localBinder(); if (!local) { BpBinder *proxy = binder->remoteBinder(); if (proxy == NULL) { ALOGE("null proxy"); } const int32_t handle = proxy ? proxy->handle() : 0; obj.type = BINDER_TYPE_HANDLE; obj.handle = handle; obj.cookie = NULL; } else { obj.type = BINDER_TYPE_BINDER; obj.binder = local->getWeakRefs(); obj.cookie = local; } } else { obj.type = BINDER_TYPE_BINDER; obj.binder = NULL; obj.cookie = NULL; } return finish_flatten_binder(binder, obj, out); }
|
Parcel 其实还有 writeWeakBinder,但是这里只管 writeStrongBinder,而且一般也是 strong 用的多。先说说 flat_binder_object
这个东西,它是 kernel binder 驱动里面的一个结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| struct flat_binder_object { unsigned long type; unsigned long flags; union { void *binder; signed long handle; }; void *cookie; };
|
这个注释就已经真相了,这个玩意就是专门拿来传 binder 对象的(前面说了还有 fd),而且 offsets 就是这个东西的在传递数据中的位置,就是前面说的 parcel 中那个 mObjects 其实是个偏移来的啦。然后 binder 和 handle 是一个 union,就是说这个 binder 对象要么是 Bn(local),要么是 Bp(remote)。
那么我接下去看 finish_flatten_binder
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| inline static status_t finish_flatten_binder( const sp<IBinder>& binder, const flat_binder_object& flat, Parcel* out) { return out->writeObject(flat, false); } status_t Parcel::writeObject(const flat_binder_object& val, bool nullMetaData) { const bool enoughData = (mDataPos+sizeof(val)) <= mDataCapacity; const bool enoughObjects = mObjectsSize < mObjectsCapacity; if (enoughData && enoughObjects) { restart_write: *reinterpret_cast<flat_binder_object*>(mData+mDataPos) = val; if (nullMetaData || val.binder != NULL) { mObjects[mObjectsSize] = mDataPos; acquire_object(ProcessState::self(), val, this); mObjectsSize++; } if (val.type == BINDER_TYPE_FD) { if (!mAllowFds) { return FDS_NOT_ALLOWED; } mHasFds = mFdsKnown = true; } return finishWrite(sizeof(flat_binder_object)); } if (!enoughData) { const status_t err = growData(sizeof(val)); if (err != NO_ERROR) return err; } if (!enoughObjects) { size_t newSize = ((mObjectsSize+2)*3)/2; size_t* objects = (size_t*)realloc(mObjects, newSize*sizeof(size_t)); if (objects == NULL) return NO_MEMORY; mObjects = objects; mObjectsCapacity = newSize; } goto restart_write; }
|
最后是调用 writeObject(object 只能是 flat_binder_object
) 来写到 parcel 中去。这里和前面的差不多,都得先判断空间够不够,但是这里还得多判断一个 mObjects 的空间够不够。不够的和前面一样调用 growData 去调整大小。这里同样多处理一个 mObjects 空间调整,这里很简单了就是 realloc 一下就行了。这里看代码就知道 flat_binder_object
是保存在 mData 的区域的,而且后面几句代码彻底说明了 mObjects 保存的是偏移地址。最后 finishWrite 和前面一样,把 mDataPos 移动一下。
这里多嘴说下 acquire_object
,我就说我烦 android 的那个啥智能指针,也不是太智能的样子,这里还得显示的增加下引用计数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| void Parcel::acquireObjects() { const sp<ProcessState> proc(ProcessState::self()); size_t i = mObjectsSize; uint8_t* const data = mData; size_t* const objects = mObjects; while (i > 0) { i--; const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(data+objects[i]); acquire_object(proc, *flat, this); } } void acquire_object(const sp<ProcessState>& proc, const flat_binder_object& obj, const void* who) { switch (obj.type) { case BINDER_TYPE_BINDER: if (obj.binder) { LOG_REFS("Parcel %p acquiring reference on local %p", who, obj.cookie); static_cast<IBinder*>(obj.cookie)->incStrong(who); } return; case BINDER_TYPE_WEAK_BINDER: if (obj.binder) static_cast<RefBase::weakref_type*>(obj.binder)->incWeak(who); return; case BINDER_TYPE_HANDLE: { const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle); if (b != NULL) { LOG_REFS("Parcel %p acquiring reference on remote %p", who, b.get()); b->incStrong(who); } return; } case BINDER_TYPE_WEAK_HANDLE: { const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle); if (b != NULL) b.get_refs()->incWeak(who); return; } case BINDER_TYPE_FD: { return; } } ALOGD("Invalid object type 0x%08lx", obj.type); }
|
然后 parcel 中内存分配应该是这样的:
然后打包了 flat_binder_object
的 parcel 就传到 kernel 的 binder 驱动里面去了。驱动里面有做特殊处理的,驱动里的处理放到后面一篇说 ServiceManager 那里细说,这里只要知道驱动里面倒腾了一下就到目标进程了,然后目标进程可以使用 parcel 的读接口读到之前写的 binder 对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| sp<IBinder> Parcel::readStrongBinder() const { sp<IBinder> val; unflatten_binder(ProcessState::self(), *this, &val); return val; } inline static status_t finish_unflatten_binder( BpBinder* proxy, const flat_binder_object& flat, const Parcel& in) { return NO_ERROR; } status_t unflatten_binder(const sp<ProcessState>& proc, const Parcel& in, sp<IBinder>* out) { const flat_binder_object* flat = in.readObject(false); if (flat) { switch (flat->type) { case BINDER_TYPE_BINDER: *out = static_cast<IBinder*>(flat->cookie); return finish_unflatten_binder(NULL, *flat, in); case BINDER_TYPE_HANDLE: *out = proc->getStrongProxyForHandle(flat->handle); return finish_unflatten_binder( static_cast<BpBinder*>(out->get()), *flat, in); } } return BAD_TYPE; }
|
readStrongBinder 其实挺简单的,是本地的可以直接用,远程的那个 getStrongProxyForHandle 也是放到后面 ServiceManager 再细说。到这里目标进程就收到原始进程传递过来的 binder 对象了,然后可以转化为 binder 的 interface 调用对应的 IPC 接口。
然后最后看下清理的情况,由于 binder 实现的接口中,Parcel 基本都是局部变量,所以 IPC 调用一结束,就会调用析构函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| Parcel::~Parcel() { freeDataNoInit(); } void Parcel::freeDataNoInit() { if (mOwner) { mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie); } else { releaseObjects(); if (mData) free(mData); if (mObjects) free(mObjects); } } void Parcel::releaseObjects() { const sp<ProcessState> proc(ProcessState::self()); size_t i = mObjectsSize; uint8_t* const data = mData; size_t* const objects = mObjects; while (i > 0) { i--; const flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(data+objects[i]); release_object(proc, *flat, this); } } void release_object(const sp<ProcessState>& proc, const flat_binder_object& obj, const void* who) { switch (obj.type) { case BINDER_TYPE_BINDER: if (obj.binder) { LOG_REFS("Parcel %p releasing reference on local %p", who, obj.cookie); static_cast<IBinder*>(obj.cookie)->decStrong(who); } return; case BINDER_TYPE_WEAK_BINDER: if (obj.binder) static_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who); return; case BINDER_TYPE_HANDLE: { const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle); if (b != NULL) { LOG_REFS("Parcel %p releasing reference on remote %p", who, b.get()); b->decStrong(who); } return; } case BINDER_TYPE_WEAK_HANDLE: { const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle); if (b != NULL) b.get_refs()->decWeak(who); return; } case BINDER_TYPE_FD: { if (obj.cookie != (void*)0) close(obj.handle); return; } } ALOGE("Invalid object type 0x%08lx", obj.type); }
|
这里的函数都不复杂,我直接贴到底了。其实就是 free 掉之前 malloc 的 mData 和 mObjects,还有就是前面手动增加了的引用计数,这里得再手动减少(这玩意就是麻烦)。
其实传递 binder 对象,最关键的地方其实在 kernel 的 binder 驱动里面,但是鉴于这篇已经够长了,而且这个和 ServiceManager 关系也挺密切的,所以决定把这块地方放到 ServiceManager 那篇去。
Parcel 是 android binder 通信中扮演着数据打包、解包的角色,是比较重要的一个东西。它的内存结构其实很简单,以后自己用的时候要注意下遵守规则(读、写顺序一致)。然后它可以传递一些小数据、也还可以传一些二进制流,对于大型数据提供匿名共享内存(Ashmem)的支持,它还有一个很特殊的功能,就是传递 binder 对象,保证了 binder IPC 通信的正常使用。